package com.sos
{
	import com.sos.comm.Call;
	import com.sos.comm.CallErrorEvent;
	import com.sos.comm.CallEvent;
	import com.sos.comm.CallFactory;
	import com.sos.comm.CallFailEvent;
	import com.sos.comm.CallFailType;
	import com.sos.comm.CallSequence;
	import com.sos.comm.CallSequenceAfterExecutionEvent;
	import com.sos.comm.CallSequenceBeforeExecutionEvent;
	import com.sos.comm.CallSequenceCompletedEvent;
	import com.sos.comm.CallSequenceFailedEvent;
	import com.sos.comm.CallStatus;
	import com.sos.comm.CallSuccessEvent;
	import com.sos.config.Configuration;
	import com.sos.core.DataObject;
	import com.sos.core.game.GameList;
	import com.sos.core.Player;
	import com.sos.core.PlayerList;
	import com.sos.core.User;
	import com.sos.core.cards.CardManager;
	import com.sos.core.cards.CardManagerEvent;
	import com.sos.core.cards.Collection;
	import com.sos.core.cards.Deck;
	import com.sos.logging.IDumpable;
	import com.sos.logging.Logger;
	import com.sos.utils.ErrorUtils;
	
	import flash.system.Capabilities;
	import flash.system.Security;
	

	public class Application implements IDumpable
	{

		// -------------------------------------------------------------------------------
		// CONSTANTS
		// -------------------------------------------------------------------------------	
		
		// 
		protected static var 	SEQUENCE_PARAM_USER_ID:String		= "user_id";
		protected static var 	SEQUENCE_PARAM_DECK_ID:String		= "deck_id";
		
		// Application states
		public static const		STATE_START:String					= "StartState";
		public static const		STATE_LOGIN:String					= "StateLogin";
		public static const		STATE_MAIN_MENU:String				= "StateMenu";
		public static const		STATE_DECK_EDITOR:String			= "StateDeckEditor";
		public static const		STATE_DEBUG:String					= "StateDebug";
		
		// Application version
		public static const		NAME:String							= "Schools Of Soccer";
		public static const		VERSION:Version						= new Version(VersionType.ALPHA,
																				  0, 			// major
																				  3, 			// minor
																				  5, 			// release
																				  "75",			// build
																				  "Franco");	// codename
		
		
		
		// -------------------------------------------------------------------------------
		// STATIC
		// -------------------------------------------------------------------------------
		
		private static const _instance:Application = new Application();
		
		public static function get instance():Application
		{
			return _instance;
		}	
		
		// -------------------------------------------------------------------------------
		// FIELDS
		// -------------------------------------------------------------------------------
		
		private var _initialized:Boolean;
		private var _user:User;
		private var _collection:Collection;
		private var _config:Configuration;
		
		// LT_TODO !!!!! TO REMOVE !!!!!
		private var _password:String; // User password
		// LT_TODO !!!!! TO REMOVE !!!!!
		
		// -------------------------------------------------------------------------------
		// CONSTRUCTOR
		// -------------------------------------------------------------------------------
		
		public function Application()
		{
			if(_instance)
				throw new Error( "Application Singleton can only be accessed through Application.instance" );
		}
		
		// -------------------------------------------------------------------------------
		// PROPERTIES
		// -------------------------------------------------------------------------------
		
		public function get isInit():Boolean
		{
			return _initialized;
		}
		
		public function get isDebug():Boolean
		{
			return _config.debugOptions.enabled;
		}
		
		public function get isUserLogged():Boolean
		{
			return _user.isLoggedIn;
		}
		
		public function get config():Configuration
		{
			return _config;
		}	
		
		public function get user():User
		{
			return _user;
		}

		protected function set user(value:User):void
		{
			_user = value;
		}

		public function get collection():Collection
		{
			return _collection;
		}
		
		protected function set collection(value:Collection):void
		{
			_collection = value;
		}
		
		public function get hasCollection():Boolean
		{
			return _collection.loaded;
		}

		
		// -------------------------------------------------------------------------------
		// PRIVATE METHODS
		// -------------------------------------------------------------------------------
		
		// Initialization
		
		private function initSystem():void
		{
		}
		
		private function initLog():void
		{
			try
			{
				// Iinit the logger
				Logger.init(_config.debugOptions, _config.logOptions);
			} 
			catch(error:Error) 
			{
				// Compose the message
				var messages:Array = new Array();
				messages.push("Log init failed: " + error.message);
				messages.push("");
				messages.push(ErrorUtils.toArray(error));
				// Trace the error
				traceError(messages);
				// LT_TODO: gestire un evento per la centralizzazione degli errori a livello applicazione 
				// throw new Error(message);
			}
		}
		
		private function initData():void
		{
			// CardManager loading
			var cardManager:CardManager = CardManager.getCardManager();
			cardManager.addEventListener(CardManagerEvent.EVENT_LOAD_COMPLETED, cardManagerLoadCompleted);
			cardManager.addEventListener(CardManagerEvent.EVENT_LOAD_FAILED, 	cardManagerLoadFailed);
			cardManager.load();
			// Data initialization
			_user		= new User();
			_collection	= new Collection();
		}
		
		private function checkInit():void
		{
			if (!_instance.isInit) throwNotInitError(); 
		}
		
		// User interface and events
		
		// Tracing methods
		
		private function formatMessage(message:String = "", pad:Boolean = false):String
		{
			return Logger.format(message, pad);
		}
		
		private function traceMessage(messages:Array):void
		{
			trace("");
			trace(formatMessage("APPLICATION MESSAGE", true));
			for (var i:int = 0; i < messages.length; i++) trace(formatMessage(messages[i]));
			trace(formatMessage("", true));
			trace("");			
		}
		
		private function traceError(messages:Array):void
		{
			trace("");
			trace(formatMessage("APPLICATION ERROR", true));
			for (var i:int = 0; i < messages.length; i++) trace(formatMessage(messages[i]));
			trace(formatMessage("", true));
			trace("");
		}
		
		// CardManager event handlers
		
		private function cardManagerLoadCompleted(e:CardManagerEvent):void
		{
			Logger.debug("CardManager loaded cards: " + e.cardManager.count);
		}
		
		private function cardManagerLoadFailed(e:CardManagerEvent):void
		{
			Logger.debug("CardManager failed: " + e.message);
		}
		
		// Utility methods
		
		/**
		 * Method automatically called after application init, placeholder to contain code to test 
		 */
		protected function test():void
		{
			
			
			Logger.info("--- APPLICATION TEST --------------------");
			Logger.info("");
			
			
			//var json:String = JsonUtils.encode(Application.instance.config);
			//var json2:String = JSON.stringify(Application.instance.config);
			// Logger.debug("config json 1: " + json);
			// Logger.debug("config json 2: " + json2);
			
			/*
			// EnumTest01
			
			var e1:EnumTest01 = EnumTest01.ITEM01;
			
			var e2:EnumTest02 = EnumTest02.YES;
			
			
			var e01Item01:EnumTest01 = EnumTest01.ITEM01;
			var e01Item02:EnumTest01 = EnumTest01.ITEM02;
			
			var e01ItemById:EnumTest01 = EnumTest01.getById(1);
			
			var e01List:Array = EnumTest01.list();
			var e01Names:Array = EnumTest01.names();
			
			Logger.debug("EnumTest01 item 1: {0}", e01Item01);
			Logger.debug("EnumTest01 item 2: {0}", e01Item02);
			
			// EnumTest02
			
			var e02Item01:EnumTest02 = EnumTest02.YES;
			var e02Item02:EnumTest02 = EnumTest02.NO;
			
			var e02ItemById:EnumTest02 = EnumTest02.getById(4);
			
			var e02ItemBySign:EnumTest02 = EnumTest02.getBySign("s");
			
			var e02List:Array = EnumTest02.list();
			var e02Names:Array = EnumTest02.names();
			
			Logger.debug("EnumTest02 item 1: {0}", e02Item01);
			Logger.debug("EnumTest02 item 2: {0}", e02Item02);
			
			
			Logger.info("");
			Logger.info("--- APPLICATION TEST --------------------");
			*/
		}
		
		// Error throwing
		
		private function throwNotInitError():void
		{
			throw new Error("Application is not initialized!");
		}
		
		// -------------------------------------------------------------------------------
		// PUBLIC METHODS
		// -------------------------------------------------------------------------------
		
		/**
		 * Initialize the application 
		 * @param ui Main UI reference used by the application
		 * @param config
		 */		
		public function init(config:Configuration):void
		{
			// Save the configuration
			_config = config;
			// Flex system init
			initSystem();
			// Log init
			initLog();
			// Data init
			initData();
			// Init done
			_initialized = true;
			// Log application start (LT_TODO > aggiungere un po di dati del client)
			Logger.info("");
			Logger.info(formatMessage("", true));
			Logger.info(formatMessage(com.sos.Application.NAME));
			Logger.info(formatMessage("Version {0}"), com.sos.Application.VERSION.toString());
			Logger.info(formatMessage("Player: {0} (type: {1}) {2}"), Capabilities.version, Capabilities.playerType, Capabilities.isDebugger ? "DEBUG" : "");
			Logger.info(formatMessage("Sandbox: {0}"), Security.sandboxType);
			Logger.info(formatMessage("", true));
			
			// If in debug, perform the tests
			if (isDebug) test();
		}
		
		/**
		 * Executes the login process using given user autentication data. 
		 * @param username
		 * @param password
		 * @param succededCallback
		 * @param failedCallback
		 */		
		public function login(username:String, password:String, succededCallback:Function = null, failedCallback:Function = null):void
		{
			// Check if to use the old login or the new one
			if (!_config.useOldUserLoginHandling)
			{
				var message:String = "";
				// Create calls for the sequence
				var call:Call = new Call(CallFactory.CALL_LOGIN, true);
				call.params = { username: username, password: password };
				call.addEventListener(CallEvent.EVENT_CALL_SUCCESS, function(e:CallSuccessEvent):void
				{
					// Retrieve the results
					var raw_user:Object 		= e.call.data["user"];
					var raw_collection:Object 	= e.call.data;
					var raw_deck_list:Object 	= e.call.data["deckList"];
					var raw_deck:Object 		= e.call.data["activeDeck"];
					// If the is already logger, perform the user logout 
					if (_user.isLoggedIn)
					{
						logout();
					}
					// Executes the user login
					setupUserData(raw_user, raw_collection, raw_deck_list, raw_deck);
					
					// LT_TODO !!!!! TO REMOVE !!!!!
					_password = password;
					// LT_TODO !!!!! TO REMOVE !!!!!
					
					// Executes success callBack
					if (succededCallback != null) succededCallback();
				});
				call.addEventListener(CallEvent.EVENT_CALL_ERROR, function(e:CallErrorEvent):void
				{
					// Build error message
					message = "Login failed: " + e.call.error.message;
					// Call fail callback
					if (failedCallback != null) failedCallback(message);
				});
				call.addEventListener(CallEvent.EVENT_CALL_FAIL, function(e:CallFailEvent):void
				{
					// Build error message
					message = "Call '" + e.call.url + "' failed: " + e.call.error.message + " [type:" + CallFailType.getById(e.call.error.code ).name + "]";
					// Call fail callback
					if (failedCallback != null) failedCallback(message);
				});
				// Executes the login call
				call.execute();
			}
			else
			{
				// Login call sequence
				var sequence:CallSequence = new CallSequence();
				// Create calls for the sequence
				var call01:Call = new Call(CallFactory.CALL_OLD_LOGIN, true);
				var call02:Call = new Call(CallFactory.CALL_COLLECTION_LOAD, true);
				if (!_config.useOldActionCardParsing)call02 = new Call(CallFactory.CALL_COLLECTION_LOAD_COMPACT, true);
				var call03:Call = new Call(CallFactory.CALL_DECK_LIST, true);
				var call04:Call = new Call(CallFactory.CALL_DECK_LOAD, true);
				// Calls
				sequence.addCall(call01);
				sequence.addCall(call02);
				sequence.addCall(call03);
				sequence.addCall(call04);
				// Event listeners
				sequence.addEventListener(CallSequenceBeforeExecutionEvent.BEFORE_EXECUTION, function(e:CallSequenceBeforeExecutionEvent):void
					{
						// Check next call url and setup parameters
						switch (e.call.url)
						{
							
							case CallFactory.CALL_OLD_LOGIN:
								e.call.params = {username: username, password: password};
								// message = "Login in progress";
								break;
							
							case CallFactory.CALL_COLLECTION_LOAD:
								e.call.params = {userID: e.sequence.getParameter( SEQUENCE_PARAM_USER_ID ) };
								// message = "Loading collection";
								break;
							
							case CallFactory.CALL_COLLECTION_LOAD_COMPACT:
								e.call.params = {userID: e.sequence.getParameter( SEQUENCE_PARAM_USER_ID ) };
								// message = "Loading collection";
								break;
							
							case CallFactory.CALL_DECK_LIST:
								e.call.params = {userID: e.sequence.getParameter( SEQUENCE_PARAM_USER_ID ) };
								// message = "Loading deck list";
								break;
							
							case CallFactory.CALL_DECK_LOAD:
								e.call.params = {userID: e.sequence.getParameter( SEQUENCE_PARAM_USER_ID ), deckID: e.sequence.getParameter( SEQUENCE_PARAM_DECK_ID ) };
								// message = "Loading active deck";
								break;
							
							default:
								break;
						}
					}
				);
				sequence.addEventListener(CallSequenceAfterExecutionEvent.AFTER_EXECUTION, function(e:CallSequenceAfterExecutionEvent):void
					{
						// Check executed call url and setup sequence parameters
						switch (e.call.url)
						{
							case CallFactory.CALL_OLD_LOGIN:
								e.sequence.setParameter(SEQUENCE_PARAM_USER_ID, e.call.data.userID);
								break;
							case CallFactory.CALL_DECK_LIST:
								// Get deck definition array
								var deck:uint = DataObject.INVALID_ID; 
								var decks:Array = e.call.data["list"];
								// Loop over definitions
								for (var i:int = 0; i<decks.length; i++)
								{
									if (decks[i].active == 1)
									{
										deck = decks[i].id;
										break;
									}
								}
								e.sequence.setParameter(SEQUENCE_PARAM_DECK_ID, deck);
								break;
							default:
								break;
						}
					}
				);
				sequence.addEventListener(CallSequenceCompletedEvent.COMPLETED, function(e:CallSequenceCompletedEvent):void
					{
						// Retrieve the results
						var raw_user:Object 		= e.results[CallFactory.CALL_OLD_LOGIN];
						var raw_collection:Object 	= e.results[CallFactory.CALL_COLLECTION_LOAD];
						if (!Application.instance.config.useOldActionCardParsing)
						{
							raw_collection = e.results[CallFactory.CALL_COLLECTION_LOAD_COMPACT];
						}
						var raw_deck_list:Object 	= e.results[CallFactory.CALL_DECK_LIST];
						var raw_deck:Object 		= e.results[CallFactory.CALL_DECK_LOAD];
						// If the is already logger, perform the user logout 
						if (_user.isLoggedIn)
						{
							logout();
						}
						// Executes the user login
						setupUserData(raw_user, raw_collection, raw_deck_list, raw_deck);
						// Executes success callBack
						if (succededCallback != null) succededCallback();
					}
				);
				sequence.addEventListener(CallSequenceFailedEvent.FAILED, function(e:CallSequenceFailedEvent):void
					{
						// Handle error
						var message:String = "";
						if (e.cancelled)
						{
							message = "Cancelled by the user!";
						}
						else
						{
							// Check if the call is failed or in error
							if (e.call.status == CallStatus.EXECUTED_WITH_ERROR)
							{
								message = "Login faild: " + e.call.error.message;
							}
							else if (e.call.status == CallStatus.FAILED)
							{
								message = "Call '" + e.call.url + "' failed: " + e.call.error.message + " [type:" + CallFailType.getById(e.call.error.code ).name + "]";
							}
						}
						// Executes failed sequence callback
						if (failedCallback != null) failedCallback(message);
					}
				);
				// Execution
				sequence.execute();
			}
		}
		
		/**
		 * Logout the current loggd user and clears all the data 
		 */		
		public function logout():void
		{
			// INFO > User login
			Logger.info("User {0} logout!", _user.username);
			// Clears the application data
			_collection.unload();
			_user.unload();
		}

		/*
		 * Updates the logged user game list
		 */
		public function updateUserGames(succededCallback:Function = null, failedCallback:Function = null):void
		{
			var message:String = "";
			// Create calls for the sequence
			var call:Call = new Call(CallFactory.CALL_USER_GAME_LIST, true);
			call.params = { userID: _user.id, password: _password };
			call.addEventListener(CallEvent.EVENT_CALL_SUCCESS, function(e:CallSuccessEvent):void
			{
				// Retrieve the results
				var raw_game_list:Object = e.call.data["list"];
				// Create game list from data
				var gameList:GameList = new GameList(raw_game_list);
				// Updates the current user game list
				_user.updateGames(gameList);
				// Executes success callBack
				if (succededCallback != null) succededCallback();
			});
			call.addEventListener(CallEvent.EVENT_CALL_ERROR, function(e:CallErrorEvent):void
			{
				// Build error message
				message = "Update user game list failed: " + e.call.error.message;
				// Call fail callback
				if (failedCallback != null) failedCallback(message);
			});
			call.addEventListener(CallEvent.EVENT_CALL_FAIL, function(e:CallFailEvent):void
			{
				// Build error message
				message = "Call '" + e.call.url + "' failed: " + e.call.error.message + " [type:" + CallFailType.getById(e.call.error.code ).name + "]";
				// Call fail callback
				if (failedCallback != null) failedCallback(message);
			});
			// Executes the login call
			call.execute();
		}
		
		/*
		 * Updates the logged user game list
		 */
		public function updateUserFriends(succededCallback:Function = null, failedCallback:Function = null):void
		{
			var message:String = "";
			// Create calls for the sequence
			var call:Call = new Call(CallFactory.CALL_USER_FRIENDS_LIST, true);
			call.params = { userID: _user.id, password: _password };
			call.addEventListener(CallEvent.EVENT_CALL_SUCCESS, function(e:CallSuccessEvent):void
			{
				// Retrieve the results
				var raw_player_list:Object = e.call.data["list"];
				// Create game list from data
				var playerList:PlayerList = new PlayerList(raw_player_list);
				// Updates the current user game list
				_user.updateFriends(playerList);
				// Executes success callBack
				if (succededCallback != null) succededCallback();
			});
			call.addEventListener(CallEvent.EVENT_CALL_ERROR, function(e:CallErrorEvent):void
			{
				// Build error message
				message = "Update user game list failed: " + e.call.error.message;
				// Call fail callback
				if (failedCallback != null) failedCallback(message);
			});
			call.addEventListener(CallEvent.EVENT_CALL_FAIL, function(e:CallFailEvent):void
			{
				// Build error message
				message = "Call '" + e.call.url + "' failed: " + e.call.error.message + " [type:" + CallFailType.getById(e.call.error.code ).name + "]";
				// Call fail callback
				if (failedCallback != null) failedCallback(message);
			});
			// Executes the login call
			call.execute();
		}
		
		/**
		 * Creates a new game for the choosed opponent
		 */
		public function makeNewGame(opponent:Player, succededCallback:Function = null, failedCallback:Function = null):void
		{
			var message:String = "";
			// Create calls for the sequence
			var call:Call = new Call(CallFactory.DEBUG_CALL_GAME_MAKE, true);
			call.params = { userID: _user.id, password: _password, opponent: opponent.id };
			call.addEventListener(CallEvent.EVENT_CALL_SUCCESS, function(e:CallSuccessEvent):void
			{
				// Retrieve the new game id
				var gameId:uint = uint(e.call.data["gameId"]);
				// Executes success callBack
				if (succededCallback != null) succededCallback(gameId);
			});
			call.addEventListener(CallEvent.EVENT_CALL_ERROR, function(e:CallErrorEvent):void
			{
				// Build error message
				message = "Update user game list failed: " + e.call.error.message;
				// Call fail callback
				if (failedCallback != null) failedCallback(message);
			});
			call.addEventListener(CallEvent.EVENT_CALL_FAIL, function(e:CallFailEvent):void
			{
				// Build error message
				message = "Call '" + e.call.url + "' failed: " + e.call.error.message + " [type:" + CallFailType.getById(e.call.error.code ).name + "]";
				// Call fail callback
				if (failedCallback != null) failedCallback(message);
			});
			// Executes the login call
			call.execute();
		}
		
		/**
		 * 
		 * @param userData
		 * @param collectionData
		 * @param deckListData
		 * @param activeDeckData
		 */
		public function setupUserData(userData:Object, collectionData:Object, deckListData:Object, activeDeckData:Object):void
		{
			// User data loading
			_user.load(userData);
			// Collection loading
			_collection.load(collectionData);
			// Decks list creation 
			var decks:Array = deckListData.list;
			// Active deck reference
			var activeDeck:Deck = null;
			// Loop over definitions
			for (var i:int = 0; i<decks.length; i++)
			{
				// Deck creation
				var deckData:Object = decks[i];
				var deck:Deck = _collection.createNewDeck(deckData.id, deckData.name);
				// Sets the active deck
				if (deckData.active == 1) activeDeck = deck;
			}
			// Load the current active deck
			activeDeck.load(activeDeckData);
			// Set the active deck
			_collection.activeDeck = activeDeck;
			// INFO > User login
			Logger.info("User {0} login!", _user.username);
		}
		
		/**
		 * 
		 * @param relativeUrl
		 * @return  
		 */		
		public function getAbsoluteCallUrl(relativeUrl:String):String
		{
			return Application.instance.config.callsUrl + relativeUrl;
		}
		
		/**
		 *  
		 * @param addDelimiters
		 * @return 
		 */		
		public function dump(source:Array = null, indentDepth:int = 0):Array
		{
			// Flag to add decoretors
			var decorate:Boolean = source == null || source.length == 0; 
			// 
			var dump:Array = source == null ? new Array() : source;

			// Decorator
			if (decorate) dump.push("");
			if (decorate) dump.push(formatMessage(NAME.toUpperCase() + " [v " + VERSION + "]", true));
			dump.push(formatMessage(""));
			dump.push(formatMessage("<< CONFIGURATION -------------------------------------"));
			dump.push(formatMessage(""));
			dump = _config.dump(dump, 0);
			dump.push(formatMessage(""));
			dump.push(formatMessage("<< USER ----------------------------------------------"));
			dump.push(formatMessage(""));
			dump = _user.dump(dump, 0);
			dump.push(formatMessage(""));
			dump.push(formatMessage("<< COLLECTION ----------------------------------------"));
			dump.push(formatMessage(""));
			dump = _collection.dump(dump, 0);
			
			// // Decorator footer
			if (decorate) dump.push(formatMessage(""));
			if (decorate) dump.push(formatMessage(NAME.toUpperCase() + " [v " + VERSION + "]", true));
			if (decorate) dump.push("");
			
			return dump;
		}
		
		// -------------------------------------------------------------------------------
		// STATIC METHODS
		// -------------------------------------------------------------------------------		
		
	}
}